interface WhiteScreenOptions {
    skeletonProject?: boolean // 是否有骨架屏
    whiteBoxElements?: string[] // 容器元素选择器列表
    checkInterval?: number // 检测间隔(ms)
    samplingPoints?: number // 采样点数量
    emptyThreshold?: number // 空点阈值
    watchRouteChanges?: boolean
}

interface WhiteScreenResult {
    status: "ok" | "error" // 检测状态
    emptyPoints?: number // 空点数
    message?: string // 附加信息
}

export class WhiteScreenDetector {
    private whiteLoopNum = 0
    private skeletonInitList: string[] = []
    private skeletonNowList: string[] = []
    private loopTimer: number | null = null
    private readonly defaultWhiteBoxElements = ["html", "body", "#app", "#root"]

    constructor(
        private callback: (result: WhiteScreenResult) => void,
        private options: WhiteScreenOptions = {}
    ) {
        this.options = {
            whiteBoxElements: this.defaultWhiteBoxElements,
            checkInterval: 1000, // 白屏检测间隔时间
            samplingPoints: 9,
            emptyThreshold: 17, // 9水平 + 8垂直(中心点不重复计算)
            ...options
        }
        // 如果启用路由监听，则初始化
        if (this.options.watchRouteChanges) {
            this.setupRouterListener()
        }
    }

    private setupRouterListener() {
        const originalPushState = history.pushState
        const originalReplaceState = history.replaceState

        history.pushState = (...args) => {
            originalPushState.apply(history, args)
            this.handleRouteChange()
        }

        history.replaceState = (...args) => {
            originalReplaceState.apply(history, args)
            this.handleRouteChange()
        }

        window.addEventListener("popstate", this.handleRouteChange.bind(this))
    }

    private handleRouteChange() {
        // 重置检测状态，确保重新采样
        this.stop()

        setTimeout(() => {
            this.start()
        }, 300)
    }

    public start(): void {
        if (this.loopTimer) return // 如果已在检测，则跳过
        if (this.options.skeletonProject) {
            if (document.readyState !== "complete") {
                this.sampling()
            }
        } else {
            if (document.readyState === "complete") {
                this.sampling()
            } else {
                window.addEventListener("load", this.sampling.bind(this))
            }
        }
    }

    public stop(): void {
        if (this.loopTimer) {
            clearTimeout(this.loopTimer)
            this.loopTimer = null
        }
    }

    private getSelector(element: Element): string {
        if (element.id) {
            return `#${element.id}`
        } else if (element.className && typeof element.className === "string") {
            return `.${element.className
                .split(" ")
                .filter(item => !!item)
                .join(".")}`
        }
        return element.nodeName.toLowerCase()
    }

    private isContainer(element: Element): boolean {
        const selector = this.getSelector(element)
        if (this.options.skeletonProject) {
            this.whiteLoopNum ? this.skeletonNowList.push(selector) : this.skeletonInitList.push(selector)
        }
        return this.options.whiteBoxElements!.indexOf(selector) !== -1
    }

    private sampling(): void {
        let emptyPoints = 0
        const points = this.options.samplingPoints!

        for (let i = 1; i <= points; i++) {
            // 水平方向采样
            const xElements = document.elementsFromPoint((window.innerWidth * i) / (points + 1), window.innerHeight / 2)
            if (this.isContainer(xElements[0])) emptyPoints++

            // 垂直方向采样(跳过中心点)
            if (i !== Math.ceil(points / 2)) {
                const yElements = document.elementsFromPoint(window.innerWidth / 2, (window.innerHeight * i) / (points + 1))
                if (this.isContainer(yElements[0])) emptyPoints++
            }
        }
        // 判断是否白屏
        if (emptyPoints < this.options.emptyThreshold!) {
            // 正常渲染
            this.handleNormalRender()
        } else {
            // 可能白屏
            this.handlePossibleWhiteScreen(emptyPoints)
        }

        this.callback({
            status: emptyPoints >= this.options.emptyThreshold! ? "error" : "ok",
            emptyPoints,
            message: emptyPoints >= this.options.emptyThreshold! ? "High number of empty points detected" : undefined
        })
    }

    private handleNormalRender(): void {
        if (this.options.skeletonProject) {
            if (!this.whiteLoopNum) {
                this.startWhiteLoop()
                return
            }
            // 比较前后DOM结构
            if (this.skeletonNowList.join() === this.skeletonInitList.join()) {
                this.callback({
                    status: "error",
                    message: "DOM structure not changed with skeleton project"
                })
                return
            }
        }
        this.stop()
    }

    private handlePossibleWhiteScreen(emptyPoints: number): void {
        if (!this.loopTimer) {
            this.startWhiteLoop()
        }
    }

    private startWhiteLoop(): void {
        this.stop()
        this.loopTimer = window.setTimeout(() => {
            if (this.options.skeletonProject) {
                this.whiteLoopNum++
                this.skeletonNowList = []
            }
            this.sampling()
        }, this.options.checkInterval)
    }
}
